package com.cyy.view.learn

import com.cyy.model.GmodelModel
import com.jfinal.kit.Kv
import javafx.scene.control.ContextMenu
import javafx.scene.control.MenuItem
import org.fxmisc.richtext.CodeArea
import org.fxmisc.richtext.LineNumberFactory
import tornadofx.*
import java.util.regex.Pattern
import org.fxmisc.richtext.model.StyleSpansBuilder
import org.fxmisc.richtext.model.StyleSpans
import java.time.Duration
import java.util.*

class RichText : View("RichText") {
    val gm: GmodelModel by inject()
    val engine = gm.engine.value
    var kv = Kv.create()

    val strOut = stringProperty()
    val ca = CodeArea()
    val mi1 = MenuItem("渲染当前行")
    val mi2 = MenuItem("渲染选中")
    val mi3 = MenuItem("渲染所有")
    val mClear = MenuItem("清除")
    override val root = borderpane {
        top=hbox {
            button("Render") {
                action {

                }
            }
        }
        center =hbox{
            add(ca)
            add(
                textarea(strOut) {
                    prefHeight = 600.0
//                        required()
                    isWrapText = true
                    isEditable = false
                    style {
                        fontSize = 16.px
                    }
                }
            )
        }
    }

    fun prepareCA(ca:CodeArea) {
        ca.setParagraphGraphicFactory(LineNumberFactory.get(ca))
        ca.isAutoScrollOnDragDesired=true
        ca.prefWidth=500.0
//        mi1.setAccelerator(KeyCombination.valueOf("SHIFT+1"))
        mi1.setOnAction {
            // 渲染当前行内容
//            println(ca.getText(ca.currentParagraph))
//            strOut.value = engine.getTemplateByString(ca.getText(ca.currentParagraph)).renderToString(kv)
            strOut.value =renderStr(ca.getText(ca.currentParagraph),kv)
        }
        mi2.setOnAction {
//            println(ca.selectedText)
//            strOut.value = engine.getTemplateByString(ca.selectedText).renderToString(kv)
            strOut.value =renderStr(ca.selectedText,kv)
        }
        mi3.setOnAction {
//            println(ca.text)
//            strOut.value = engine.getTemplateByString(ca.text).renderToString(kv)
            strOut.value =renderStr(ca.text,kv)
        }
        mClear.setOnAction {
            ca.replaceText("")
        }

        ca.contextMenu = ContextMenu(mi1)
        ca.contextMenu.items.addAll(mi2, mi3,mClear)
        var cleanupWhenNoLongerNeedIt = ca

                // plain changes = ignore style changes that are emitted when syntax highlighting is reapplied
                // multi plain changes = save computation by not rerunning the code multiple times
                //   when making multiple changes (e.g. renaming a method at multiple parts in file)
                .multiPlainChanges()

                // do not emit an event until 500 ms have passed since the last emission of previous stream
                .successionEnds(Duration.ofMillis(500))

                // run the following code block when previous stream emits an event
                .subscribe({ ignore -> ca.setStyleSpans(0, computeHighlighting(ca.text)) })
        ca.replaceText(0,0, sampleCode)

    }
    fun renderStr(inStr:String,kv:Kv):String{
        return engine.getTemplateByString(inStr).renderToString(kv)
    }

    init {
        prepareCA(ca)
    }
}

private val KEYWORDS = arrayOf("abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else", "enum", "extends", "final", "finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long", "native", "new", "package", "private", "protected", "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "try", "void", "volatile", "while")
private val KEYWORD_PATTERN = "\\b(" + KEYWORDS.joinToString("|") + ")\\b"
private val PAREN_PATTERN = "\\(|\\)"
private val BRACE_PATTERN = "\\{|\\}"
private val BRACKET_PATTERN = "\\[|\\]"
private val SEMICOLON_PATTERN = "\\;"
private val STRING_PATTERN = "\"([^\"\\\\]|\\\\.)*\""
private val COMMENT_PATTERN = "//[^\n]*" + "|" + "/\\*(.|\\R)*?\\*/"

val PATTERN = Pattern.compile(
        "(?<KEYWORD>" + KEYWORD_PATTERN + ")"
        + "|(?<PAREN>" + PAREN_PATTERN + ")"
        + "|(?<BRACE>" + BRACE_PATTERN + ")"
        + "|(?<BRACKET>" + BRACKET_PATTERN + ")"
        + "|(?<SEMICOLON>" + SEMICOLON_PATTERN + ")"
        + "|(?<STRING>" + STRING_PATTERN + ")"
        + "|(?<COMMENT>" + COMMENT_PATTERN + ")"
)

private val sampleCode = arrayOf("package com.example;", "", "import java.util.*;", "", "public class Foo extends Bar implements Baz {", "", "    /*", "     * multi-line comment", "     */", "    public static void main(String[] args) {", "        // single-line comment", "        for(String arg: args) {", "            if(arg.length() != 0)", "                System.out.println(arg);", "            else", "                System.err.println(\"Warning: empty string as argument\");", "        }", "    }", "", "}").joinToString("\n")

private fun computeHighlighting(text: String): StyleSpans<Collection<String>> {
    val PATTERN = Pattern.compile(
            "(?<KEYWORD>" + KEYWORD_PATTERN + ")"
                    + "|(?<PAREN>" + PAREN_PATTERN + ")"
                    + "|(?<BRACE>" + BRACE_PATTERN + ")"
                    + "|(?<BRACKET>" + BRACKET_PATTERN + ")"
                    + "|(?<SEMICOLON>" + SEMICOLON_PATTERN + ")"
                    + "|(?<STRING>" + STRING_PATTERN + ")"
                    + "|(?<COMMENT>" + COMMENT_PATTERN + ")"
    )
    val matcher = PATTERN.matcher(text)
    var lastKwEnd = 0
    val spansBuilder = StyleSpansBuilder<Collection<String>>()
    while (matcher.find()) {
        val styleClass = (if (matcher.group("KEYWORD") != null)
            "keyword"
        else if (matcher.group("PAREN") != null)
            "paren"
        else if (matcher.group("BRACE") != null)
            "brace"
        else if (matcher.group("BRACKET") != null)
            "bracket"
        else if (matcher.group("SEMICOLON") != null)
            "semicolon"
        else if (matcher.group("STRING") != null)
            "string"
        else if (matcher.group("COMMENT") != null)
            "comment"
        else
            null)!! /* never happens */
        spansBuilder.add(Collections.emptyList(), matcher.start() - lastKwEnd)
        spansBuilder.add(Collections.singleton(styleClass), matcher.end() - matcher.start())
        lastKwEnd = matcher.end()
    }
    spansBuilder.add(Collections.emptyList(), text.length - lastKwEnd)
    return spansBuilder.create()
}


